/*
 * LinkedList.h
 *
 */

//#ifndef LINKEDLIST_H_
//#define LINKEDLIST_H_
#include "util/exceptions/InvalidParameterException.h"
#include "util/exceptions/InvalidOperationException.h"
#include "util/exceptions/ElementNotFoundException.h"
#include "util/Matcher.h"

#pragma once

namespace util {

#ifndef NULL
#define NULL 0
#endif

/* @class ListNode
 * Representa un nodo de la lista. Contiene dato generico y nodo siguiente.
 */
template<class T> class ListNode {
public:
	ListNode(T d);
	virtual ~ListNode();
	T getData();
	ListNode<T>* getNext();
	void setNext(ListNode<T>* node);
private:
	T data;
	ListNode<T>* next;
};
/* @class LinkedList
 * Representa una lista enlazada. Almacena items (elementos) genericos.
 */
template<class I> class LinkedList {
public:
	/* constructor por defecto, sin parametros */
	LinkedList();
	/* destructor por defecto, sin parametros */
	virtual ~LinkedList();
	/* agrega el item al final de la lista */
	void append(I &item);
	/* devuelve el item en la posicion index. Throws InvalidParameterException */
	I get(int index);
	/* elimina (y devuelve) el item en la posicion index. Throws InvalidParameterException */
	I remove(int index);
	/* elimina (y devuelve) el primer item de la lista. Throws InvalidOperationException */
	I removeFirst();
	/* agrega el item en la posicion index */
	void add(int index, I &item);
	/* cantidad de elementos en la lista */
	int count();
	/* me indica si la lista esta vacia */
	bool isEmpty();
	/* devuelve true si el index se encuentra dentro de los rangos validos */
	bool elementExists(int index) {
		return !this->isEmpty() || (index < this->count()) || (index > 0);
	}
	/* busca un dato dentro de la lista y lo devuelve. Throws ElementNotFoundException */
	I  search(Matcher<I>* matcher) throw (ElementNotFoundException);
	/* devuelve true si el dato se encuentra en algun nodo de la lista */
	bool contains(Matcher<I>* matcher);

	class Iterator {
	public:
		/* constructor con parametros */
		Iterator(LinkedList<I>* iteratedList) {
			if (iteratedList->isEmpty())
				this->nextNode = NULL;
			else
				this->nextNode = iteratedList->getNode(0);
		}
		/* destructor por defecto, sin parametros */
		virtual ~Iterator() {
		}

		virtual bool hasNext() {
			return (this->nextNode != NULL);
		}

		virtual I next() {
			if (!this->hasNext())
				throw InvalidOperationException("No hay siguiente en la lista");
			I data = this->nextNode->getData();
			this->nextNode = this->nextNode->getNext();
			return data;
		}

	private:
		ListNode<I>* nextNode;
	};

	/* devuelve un iterador sobre la lista */
	Iterator* iterator() {
		return new Iterator(this);
	}
	void pushAll(LinkedList<I> *destination) {
		Iterator* anIterator = destination->iterator();
		while(anIterator->hasNext()) {
			I next = anIterator->next();
			this->add(0, next);
		}
		delete anIterator;
	}
protected:
	ListNode<I>* getNode(int index);

private:
	ListNode<I>* first;
	int countNodes;
};

/* Implementacion de clase Node.
 * Se implementan los metodos en una unica linea porque son triviales.
 */
template<class T> ListNode<T>::ListNode(T d) {
	data = d;
	next = NULL;
}

template<class T> ListNode<T>::~ListNode() {
}

template<class T> T ListNode<T>::getData() {
	return data;
}

template<class T> ListNode<T>* ListNode<T>::getNext() {
	return next;
}

template<class T> void ListNode<T>::setNext(ListNode<T>* n) {
	next = n;
}

/* Implementacion de clase LinkedList.
 * Se implementan los metodos en una unica linea porque son triviales.
 */
template<class I> LinkedList<I>::LinkedList() {
	this->countNodes = 0;
	this->first = NULL;
}

template<class I> LinkedList<I>::~LinkedList() {
	while (!this->isEmpty()) {
		this->removeFirst();
	}
}

template<class I> bool LinkedList<I>::isEmpty() {
	return this->countNodes == 0;
}

template<class I> I LinkedList<I>::get(int index) {
	return this->getNode(index)->getData();
}

template<class I> ListNode<I>* LinkedList<I>::getNode(int index) {

	if (!this->elementExists(index)) {
		throw(new InvalidParameterException(
				"El index solicitado esta fuera del rango de la lista, o bien la misma se encuentra vacia"));
	}

	ListNode<I>* actualNode = this->first;
	for (int i = 0; i < index; i++) {
		actualNode = actualNode->getNext();
	}
	return actualNode;
}

template<class I> I LinkedList<I>::remove(int index) {
	// controlo que el index sea valido
	if (!this->elementExists(index)) {
		throw(new InvalidParameterException(
				"El index solicitado esta fuera del rango de la lista, o bien la misma se encuentra vacia"));
	}
	if (index == 0) {
		return this->removeFirst();
	}
	// obtengo el nodo anterior al que debo remover
	ListNode<I>* previousNode = this->getNode(index - 1);
	ListNode<I>* nodeToRemove = previousNode->getNext();
	I data = nodeToRemove->getData();
	previousNode->setNext(nodeToRemove->getNext());
	delete nodeToRemove;
	this->countNodes--;
	return data;
}

template<class I> I LinkedList<I>::removeFirst() {
	if (this->isEmpty()) {
		throw(new InvalidOperationException(
				"No se puede remover el primer elemento de la lista ya que la misma se encuentra vacia"));
	}
	ListNode<I>* firstNode = this->first;
	I data = firstNode->getData();
	this->first = firstNode->getNext();
	delete firstNode;
	this->countNodes--;
	return data;
}

template<class I> void LinkedList<I>::add(int index, I &item) {
	if (index > this->count())
		throw(new InvalidOperationException(
				"No se puede agregar un elemento mas alla de la ultima posicion"));

	ListNode<I>* newNode = new ListNode<I> (item);
	this->countNodes++;
	if (index == 0) {
		newNode->setNext(this->first);
		this->first = newNode;
		return;
	}
	ListNode<I>* previousNode = this->getNode(index - 1);
	newNode->setNext(previousNode->getNext());
	previousNode->setNext(newNode);
}

template<class I> void LinkedList<I>::append(I &item) {
	this->add(this->count(), item);
}

template<class I> int LinkedList<I>::count() {
	return countNodes;
}

template<class I> I LinkedList<I>::search(Matcher<I>* matcher) throw (ElementNotFoundException){
	Iterator* oneIterator = this->iterator();
	while (oneIterator->hasNext()){
		I dataToCompare = oneIterator->next();
		if (matcher->matches(dataToCompare)){
			delete oneIterator;
			return dataToCompare;
		}
	}
	delete oneIterator;
	//Si el dato no se encontro, tiro una excepcion
	throw ElementNotFoundException("El dato buscado no se encuentra en la lista");
}

template<class I> bool LinkedList<I>::contains(Matcher<I>* matcher){
	Iterator* oneIterator = this->iterator();
	bool matches = false;
	while (oneIterator->hasNext() && !matches){
		I next = oneIterator->next();
		matches = matcher->matches(next);
	}
	delete oneIterator;
	return matches;
}

} // fin namespace
//#endif /* LINKEDLIST_H_ */
